﻿using System;
using System.Collections.Generic;
using System.IO;
using PS.WssSync.Utils;
using PS.Utils.FPRPC;

namespace PS.WssSync
{
    internal class FileSync
    {                
        private readonly AppProperties properties;
        private readonly FrontPageRPC frontPage;
        private readonly WebHelper webHelper;
        private readonly WssSyncConfig syncConfig;

        public FileSync(AppProperties properties, WssSyncConfig syncConfig)
        {
            this.properties = properties;
            this.syncConfig = syncConfig;
            frontPage = new FrontPageRPC(properties.Credentials);
            webHelper = new WebHelper(properties.Credentials);            
        }

        public void Synchronize()
        {
            switch (properties.Operation)
            {
                case Operations.Update:
                    DownloadAllFiles();
                    break;

                case Operations.Checkout:
                    DownloadAllFiles();
                    break;

                case Operations.Commit:
                    EnumerateFiles(UploadFile);
                    break;
            }
        }

        private void DownloadAllFiles()
        {
            WebUrl folder = frontPage.UrlToWebUrl(properties.DirUrl);
            DownloadFolder(folder);
        }

        private void DownloadFolder(WebUrl folder)
        {
            List<WebUrl> files = frontPage.ListDocuments(folder, true);

            int prefixlength = folder.FileUrl.Length;
            foreach (WebUrl file in files)
            {
                string partialUrl = file.FileUrl.Substring(prefixlength);
                string path = MakePath(properties.DirPath, partialUrl);
                try
                {
                    DownloadFileDispatch(path, file, partialUrl);
                }
                catch (Exception ex)
                {
                    ConsoleHelper.WriteError(ex.Message, partialUrl);
                }
            }
        }
        
        private static string GetPartialPath(string dirParh, string filePath)
        {
            return filePath.Substring(dirParh.Length).TrimStart('\\');
        }

        private static string MakeUrl(string serverPath, string filePath)
        {
            return serverPath.TrimEnd('/') + "/" + filePath.Replace("\\", "/").TrimStart('/');
        }
        
        private static string MakePath(string dirPath, string fileUrl)
        {
            return dirPath.TrimEnd('\\') + "\\" + fileUrl.Replace("/", "\\").TrimStart('\\');
        }

        private void EnumerateFiles(Action<string, WebUrl, string> action)
        {
            if (!Directory.Exists(properties.DirPath))
            {
                throw new ApplicationException("Specified directory doesn't exists on the hard drive");
            }
            foreach (string fileName in Directory.GetFiles(properties.DirPath, "*", SearchOption.AllDirectories))
            {                
                string partialName = GetPartialPath(properties.DirPath, fileName);
                string fileUrl = MakeUrl(properties.DirUrl, partialName);

                try
                {
                    WebUrl url = frontPage.UrlToWebUrl(fileUrl);
                    action(fileName, url, partialName);
                }
                catch (Exception ex)
                {
                    ConsoleHelper.WriteError(ex.Message, partialName);
                }                
            }
        }

        private void EnsureFolderExists(WebUrl fileUrl)
        {
            List<WebUrl> foldersToCreate = new List<WebUrl>();

            string folder = fileUrl.FileUrl;
            do
            {
                int index = folder.LastIndexOf('/');
                folder = index != -1 ? folder.Substring(0, index) : string.Empty;
                WebUrl folderUrl = new WebUrl { SiteUrl = fileUrl.SiteUrl, FileUrl = folder };
                DocumentInfo info = frontPage.GetDocumentMetaInfo(folderUrl);
                if (info.Exists)
                {
                    break;
                }
                foldersToCreate.Insert(0, folderUrl);
            } while (!string.IsNullOrEmpty(folder));

            foreach (WebUrl url in foldersToCreate)
            {
                frontPage.CreateDirectory(url);
                ConsoleHelper.WriteFileAction(FileAction.DirCreated, url.FileUrl);
            }
        }

        private void UploadFile(string fileName, WebUrl url, string partialName)
        {
            if(!webHelper.IsRequiredFile(syncConfig, url))
            {
                return;
            }
           
            DocumentInfo info = frontPage.GetDocumentMetaInfo(url);            

            if (!info.Exists)
            {
                using (FileStream s = new FileStream(fileName, FileMode.Open))
                {
                    EnsureFolderExists(url);
                    frontPage.PutDocument(url, s);
                    ConsoleHelper.WriteFileAction(FileAction.Uploaded, partialName);
                }
                return;
            }
            
            using (MemoryStream stream = new MemoryStream())
            {
                frontPage.GetDocument(url, stream);
                stream.Seek(0, SeekOrigin.Begin);
                FileStream fileStream = null;
                StreamReader streamReader = null;

                try
                {
                    fileStream = new FileStream(fileName, FileMode.Open);

                    bool iswebPartPage = webHelper.IsWebPartPage(fileName);
                    bool equals;                    
                    if(iswebPartPage)
                    {
                        string webDocument = webHelper.GetDocument(url);
                        streamReader = new StreamReader(fileStream);
                        string hdDocument = streamReader.ReadToEnd();
                        equals = webHelper.EqualsWpPages(webDocument, hdDocument);
                    }
                    else
                    {
                        equals = HashUtil.GetHash(fileStream) == HashUtil.GetHash(stream);
                    }

                    if(equals)
                    {
                        ConsoleHelper.WriteFileAction(FileAction.Skipped, partialName);
                        return;
                    }

                    FileInfo fileInfo = new FileInfo(fileName);
                    fileInfo.Refresh();

                    if(!properties.Overwrite &&
                        DateTime.Compare(fileInfo.LastWriteTimeUtc, info.ModifiedDate) <= 0)
                    {
                        ConsoleHelper.WriteFileAction(FileAction.Conflicted, partialName);
                        return;
                    }

                    //if(iswebPartPage && !info.IsPublishingPage)
                    //{
                    //    webHelper.RemoveAllWebParts(url);
                    //}

                    stream.Seek(0, SeekOrigin.Begin);
                    fileStream.Seek(0, SeekOrigin.Begin);
                    frontPage.PutDocument(url, fileStream);
                    ConsoleHelper.WriteFileAction(FileAction.Overwritten, partialName);
                }
                finally
                {
                    if(null != streamReader)
                    {
                        streamReader.Dispose();
                    }

                    if(null != fileStream)
                    {
                        fileStream.Dispose();
                    }
                }                                                                                                                              
            }            
        }

        private void DownloadFileDispatch(string fileName, WebUrl url, string partialName)
        {           
            if(!webHelper.IsRequiredFile(syncConfig, url))
            {
                return;
            }

            string metaInfo = frontPage.GetDocumentMetaInfoRaw(url);
            DocumentInfo info = FrontPageRPC.ParseMetaInformationResponse(metaInfo);
            if (!info.Exists)
            {
                ConsoleHelper.WriteFileAction(FileAction.NotFoundOnServer, partialName);
            }
                      
            if(webHelper.IsWebPartPage(fileName))
            {
                DownloadFile(fileName, url, partialName, true);
                if(info.IsPublishingPage)
                {                    
                    string infoFileName = fileName + ".metainfo";
                    File.WriteAllText(infoFileName, metaInfo);
                }                
            }
            else
            {
                DownloadFile(fileName, url, partialName, false);
            }            
        }        

        private void DownloadFile(string fileName, WebUrl url, string partialName, bool isWebPartPage)
        {
            MemoryStream stream = null;
            string webDocument = null;
            DocumentInfo info;

            try
            {
                if(isWebPartPage)
                {
                    webDocument = webHelper.GetDocument(url);
                    webDocument = HtmlCleanup.RemoveDesignerTags(webDocument);
                    info = frontPage.GetDocumentMetaInfo(url);
                }
                else
                {
                    stream = new MemoryStream();
                    info = frontPage.GetDocument(url, stream);
                }

                if(!info.Exists)
                {
                    ConsoleHelper.WriteFileAction(FileAction.NotFoundOnServer, partialName);
                    return;
                }

                bool hdFileExists = File.Exists(fileName);
                bool equals = false;


                if(hdFileExists)
                {                                           
                    if(isWebPartPage)
                    {
                        using(StreamReader sr = new StreamReader(fileName))
                        {
                            string hdDocument = sr.ReadToEnd();
                            equals = webHelper.EqualsWpPages(webDocument, hdDocument);
                        }
                    }
                    else
                    {
                        using(FileStream file = new FileStream(fileName, FileMode.Open))
                        {
                            stream.Seek(0, SeekOrigin.Begin);
                            equals = HashUtil.GetHash(file) == HashUtil.GetHash(stream);
                        }
                    }
                }

                if(equals)
                {
                    ConsoleHelper.WriteFileAction(FileAction.Skipped, partialName);
                    return;
                }

                if(!properties.Overwrite && hdFileExists)
                {
                    if(Operations.Checkout == properties.Operation)
                    {
                        ConsoleHelper.WriteFileAction(FileAction.AlreadyExists, partialName);
                        return;
                    }

                    FileInfo fileInfo = new FileInfo(fileName);                      
                    fileInfo.Refresh();

                    if(Operations.Update == properties.Operation &&
                            DateTime.Compare(info.ModifiedDate, fileInfo.LastWriteTimeUtc) <= 0)
                    {
                        ConsoleHelper.WriteFileAction(FileAction.Conflicted, partialName);
                        return;
                    }
                }

                string fileDir = Path.GetDirectoryName(fileName);
                if(!Directory.Exists(fileDir))
                {
                    Directory.CreateDirectory(fileDir);
                }

                if (isWebPartPage)
                {
                    using(StreamWriter sw = new StreamWriter(fileName, false))
                    {
                        sw.Write(webDocument);
                    }
                }
                else
                {
                    using (FileStream file = new FileStream(fileName, FileMode.Create))
                    {
                        stream.Seek(0, SeekOrigin.Begin);
                        stream.CopyTo(file);
                    }
                }

                FileAction action = hdFileExists ? FileAction.Overwritten : FileAction.Downloaded;
                ConsoleHelper.WriteFileAction(action, partialName);
            }
            finally
            {
                if(null != stream)
                {
                    stream.Dispose();
                }
            }

        }
    }
}